Skip to content

(for v0.4) Add a way to jump to bootloader and/or reset settings on startup#3213

Open
rianadon wants to merge 12 commits intozmkfirmware:mainfrom
rianadon:bootloader-key-new
Open

(for v0.4) Add a way to jump to bootloader and/or reset settings on startup#3213
rianadon wants to merge 12 commits intozmkfirmware:mainfrom
rianadon:bootloader-key-new

Conversation

@rianadon
Copy link
Copy Markdown

I've built on #1757 to merge in the latest changes in ZMK. Most notably I've integrated the new boot retention mode settings and addressed the comments in the previous PR.

PR check-list

  • Branch has a clean commit history
  • Additional tests are included, if changing behaviors/core code that is testable.
  • Proper Copyright + License headers added to applicable files (Generally, we stick to "The ZMK Contributors" for copyrights to help avoid churn when files get edited)
  • Pre-commit used to check formatting of files, commit messages, etc.
  • Includes any necessary documentation changes.

joelspadin and others added 6 commits January 22, 2026 03:35
Moved the logic to select the type of reboot from behavior_reset.c to a
new zmk_reset() function. This allows rebooting to bootloader from
other code, and it gives us a starting point for future work to support
other bootloaders aside from the Adafruit nrf52 bootloader.
For now, this just clears BLE bonds, but it can be updated in the
future to handle clearing all settings.
This adds an optional feature to trigger an action if a specific key is
held when the keyboard is powered on. It can be configured to jump to
the bootloader and/or clear settings.

This is inspired by QMK's "bootmagic lite" feature, and it is primarily
intended as a way to recover a keyboard which doesn't have a physical
reset button in case it is flashed with firmware that doesn't have a
&bootloader key in its keymap. It can also be used to clear BLE bonds on
the peripheral side of a split keyboard without needing to flash
special firmware.
@rianadon rianadon requested review from a team as code owners January 23, 2026 06:32
@petejohanson petejohanson added this to the v0.4.0 milestone Jan 27, 2026
Copy link
Copy Markdown
Contributor

@petejohanson petejohanson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this! A few thoughts on the implementation.

Comment on lines +18 to +23
jump-to-bootloader:
type: boolean
description: Reboots into the bootloader.
reset-settings:
type: boolean
description: Clears settings and reboots.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be really nice, now that we have ZMK Studio, if we could also add an option here for doing a "restore stock keymap", just in case someone mistakenly removes their &studio_unlock from their keymap when making changes.

On that node, this should probably just be one properly, that's an enum, e.g.:

Suggested change
jump-to-bootloader:
type: boolean
description: Reboots into the bootloader.
reset-settings:
type: boolean
description: Clears settings and reboots.
action:
type: string
required: true
enum:
- "jump-to-bootloader"
- "reset-settings"
- "restore-studio-stock-keymap"

since it only makes sense, IMHO, for a given boot magic key to perform one of the possible boot magic actions, and allows us to grow the enum list later.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on the ZMK Studio "restore default keymap" idea - worth logging on https://github.com/zmkfirmware/zmk-studio/issues ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be even nicer if, instead of restoring the default keymap, we had an option to just unlock studio.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be even nicer if, instead of restoring the default keymap, we had an option to just unlock studio.

Yeah, like this much better.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's almost sounding like the action should just be a pointer to some ZMK behavior :)

Comment thread app/src/ble.c Outdated

// Hardcoding a reasonable hardcoded value of peripheral addresses
// to clear so we properly clear a split central as well.
for (int i = 0; i < 8; i++) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this and the loop above both iterate 8 times, lets unify this into one loop that deletes both kinds of settings.

@@ -0,0 +1,94 @@
/*
Copy link
Copy Markdown
Contributor

@petejohanson petejohanson Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm tempted to say this code should live in the fairly recently created app/src/boot/ directory at this point.

Comment thread app/src/boot_magic_key.c Outdated
@@ -0,0 +1,94 @@
/*
* Copyright (c) 2023 The ZMK Contributors
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Copyright (c) 2023 The ZMK Contributors
* Copyright (c) 2026 The ZMK Contributors

zmk_reset_settings();
}

if (config->jump_to_bootloader) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I need a refresher here, or @joelspadin can weigh in... But do we really have a use case where you want to reset the settings and then jump to the bootloader? Having a hard time imaging a real need for that specific use case.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure I probably came up with a reason for that when I originally wrote this, but I can't think of one now.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QMK does a reset then jump, but it's always bothered me that there's no way to do just a jump to bootloader. With QMK I've had some troubles with my saved changes causing conflicts when I make major changes to the hardware like changing the number of keys in the firmware, and a settings reset was necessary. I haven't stress tested ZMK as much, but if this isn't an issue here (Studio does seem very well architected), then I agree just one makes sense.

Comment thread app/src/reset.c
@@ -0,0 +1,63 @@
/*
* Copyright (c) 2023 The ZMK Contributors
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Copyright (c) 2023 The ZMK Contributors
* Copyright (c) 2026 The ZMK Contributors


:::caution

Currently this action _only_ clears BLE bonds. It will be updated to reset all settings in the future.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're really going to have this only do this, I'm wary about this changing out from under folks in the future. Can we name this current action "clear-ble-bonds" or something, and add a true "reset-settings" action later as an additional action option, instead of the current PR approach?

@joelspadin any concerns?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, I originally wrote this when the settings_reset shield only cleared BLE bonds and didn't reset all settings. I'd suggest starting with a full settings reset option. I'm not sure if there are cases where you'd want to clear BLE bonds only instead of resetting everything, but if so, we could add a separate option for that too.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main reason I can think to maintain a "clear BLE only" is to preserve any studio changes when trying to fix pairing issues between halves of a split, so I think there's a use case for both actions being possible from boot keys.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we are thinking about the options, I think it might be nice to have separate options for "clear split bonds" vs. "clear BLE profiles" (except the split ones). Not necessarily in the first iteration.

Comment thread docs/docs/features/boot-magic-key.md Outdated
Comment on lines +81 to +83
If you want a single boot magic key to perform multiple actions, simply add properties for each action to the same `zmk,boot-magic-key` node. The order of the properties does not matter.

For example, to make a key that resets settings and then reboots to the bootloader, add both `reset-settings` and `jump-to-bootloader`:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still not sure we really need this. Open to being convinced otherwise though. Thanks.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean slate when you've been tinkering with ZMK studio while getting the matrix right? Saves building and flashing a settings reset firmware too.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean a dual function key that both resets settings and jumps into the bootloader.... Seems super niche, and I'm not sure I understand your described need for studio iteration... Can you explain in exact steps what use case you're considering?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly user ignorance or misunderstanding, but I've flashed new firware onto a board to find what I though were traces of past changes I'd made in ZMK Studio. An easy way to flash new firmware and wipe out any past Studio changes seems like a good thing. And wipe old Bluetooth pairings too?

Copy link
Copy Markdown
Contributor

@caksoylar caksoylar Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can think of a scenario where a designer wants only a single bootmagic key, in which case you'd want it to do everything, including clearing all settings and jumping to bootloader.

@@ -0,0 +1,23 @@
# Copyright (c) 2023, The ZMK Contributors
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Copyright (c) 2023, The ZMK Contributors
# Copyright (c) 2026, The ZMK Contributors

@petejohanson
Copy link
Copy Markdown
Contributor

As I've thought on this more, I'm having some concerns about having "single key" magic keys.. unlike QMK, we are far more likely to have someone tapping a key to wake the device and run this early code, potentially with them tapping/accidentally tapping the 0th key to do so.

If we keep this as is, I fear we'll get a lot of accidental triggers. What do folks think about having multiple positions assignable to a given boot magic key? Doesn't need to have fancy combo handling, just enough to track "are all positions pressed that are part of this boot magic key?"

@caksoylar
Copy link
Copy Markdown
Contributor

If we keep this as is, I fear we'll get a lot of accidental triggers. What do folks think about having multiple positions assignable to a given boot magic key? Doesn't need to have fancy combo handling, just enough to track "are all positions pressed that are part of this boot magic key?"

That's a valid concern but since we aren't enabling this by default, the users should be aware of the specific key position (or the keyboard designers should tell the users), right? I think it's good to have, rather than must.

What would the effective combo term be? Is it a simple trigger where you want both keys pressed at the time of the check (so the effective term is the boot time) or do we implement logic to wait for keys?

@petejohanson
Copy link
Copy Markdown
Contributor

If we keep this as is, I fear we'll get a lot of accidental triggers. What do folks think about having multiple positions assignable to a given boot magic key? Doesn't need to have fancy combo handling, just enough to track "are all positions pressed that are part of this boot magic key?"

That's a valid concern but since we aren't enabling this by default, the users should be aware of the specific key position (or the keyboard designers should tell the users), right? I think it's good to have, rather than must.

Still might have something bump the corner of your keeb and wake it and since the check is just "is this key pressed before the timeout" you'd suddenly potentially lose all your studio customization, etc.

I think this functionality is super useful, but we also shoukd try to design this in a way that's "easy not to screw up".

I also don't trust designers/vendors to consistently communicate this, nor users to always read what is communicated by their vendor.

What would the effective combo term be? Is it a simple trigger where you want both keys pressed at the time of the check (so the effective term is the boot time) or do we implement logic to wait for keys?

I think just "are all keys held at once before the boot magic timeout"

@nmunnich
Copy link
Copy Markdown
Contributor

I wonder if "check on boot" is even the right thing to aim for, for wireless devices. I think a "check on usb insertion" might make more sense for those kind of devices. We would lose more function for any keyboards wanting to run hardware without usb support, but those sorts of devices need to be flashed with a programmer and are unlikely to have a bootloader/are more heavy enthusiast.

@petejohanson
Copy link
Copy Markdown
Contributor

I wonder if "check on boot" is even the right thing to aim for, for wireless devices. I think a "check on usb insertion" might make more sense for those kind of devices. We would lose more function for any keyboards wanting to run hardware without usb support, but those sorts of devices need to be flashed with a programmer and are unlikely to have a bootloader/are more heavy enthusiast.

I like where you're thinking! Why don't we use the hwinfo API to determine the reset reason, and ignore the boot magic unless it's one of the "valid" reasons (.e.g. reset pin or power-on-reset)? I think that might a good way to avoid the accidental triggers from a key press to wake, and allow simple one button boot magic.

@joelspadin
Copy link
Copy Markdown
Collaborator

Adding support for combos probably wouldn't be too hard either. You could add a bool[] of the same size as the key positions array (or bitfield, though that wouldn't really save space unless you do something silly like a 5+ key combo) to the driver data to store the pressed states of each of the key positions. Track both presses and releases until the timeout expires, and if every element of the array is true after a key event, trigger the boot magic logic.

@petejohanson
Copy link
Copy Markdown
Contributor

Adding support for combos probably wouldn't be too hard either. You could add a bool[] of the same size as the key positions array (or bitfield, though that wouldn't really save space unless you do something silly like a 5+ key combo) to the driver data to store the pressed states of each of the key positions. Track both presses and releases until the timeout expires, and if every element of the array is true after a key event, trigger the boot magic logic.

Yeah, my thoughts exactly. We don't need crazy "combo" logic here.

@nmunnich
Copy link
Copy Markdown
Contributor

I think it would be a good idea to predefine a bootloader boot magic key for our in-tree keyboards. Maybe top left key as a convention, mirrored for splits?

Key positions are affected by the [matrix transform](../config/kscan.md#matrix-transform), so if your keyboard has multiple transforms for alternate layouts, you may need to adjust positions according to the user's selected transform. There is no automatic way to do this, but one way to simplify things for users is to add a block of commented out code to the keymap which selects the transform and updates the key positions to match if uncommented.

For example, consider a split keyboard which has 6 columns per side by default but supports a 5-column layout, and assume you want the top-left key on the left side and the top-right key on the right side to be boot magic keys. The top-left key will be position 0 regardless of layout, but the top-right key will be position 11 by default and position 9 in the 5-column layout.
For example, consider a split keyboard which has 6 columns per side by default but supports a 5-column layout, and assume you want the top-left key on the left side and the top-right key on the right side to be boot magic combos. The top-left key will be position 0 regardless of layout, but the top-right key will be position 11 by default and position 9 in the 5-column layout.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be an issue when the selected physical layout is changed at runtime (e.g. via ZMK Studio. I think we need a way to specify this per physical layout. @petejohanson do you have a suggestion?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I originally wrote this before physical layouts were a thing, so I didn't have any consideration for dynamically switching layouts.

How is this handled for sideband behaviors?

Actually, could we maybe just use sideband behaviors instead of creating a new system for this? Could we add properties to turn a sideband behavior into a boot magic key by making it only process the sideband behavior at boot and then pass through to the regular handling after that? Supporting combos with sideband behaviors would be tricky, but I don't know if that's a hard requirement.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sideband behaviors (typically) use their own kscan, right? So it doesn't relate to physical layouts. But I might not be thinking about this right.

I was thinking perhaps multiple bootmagic nodes defined, then each physical layout gets a phandle, similar to matrix transform/kscan. Then only the bootmagic node referred to by the active physical transform is active.

@rianadon
Copy link
Copy Markdown
Author

rianadon commented Mar 2, 2026

I could use some more clarity on how to handle physical layouts here. Should the bootmagic key/combo be defined independently for each physical layout? Or defined in terms of the kscan row and column? Or keep the behavior as-is?

@peterjc
Copy link
Copy Markdown
Contributor

peterjc commented Mar 2, 2026

@rianadon I suspect you're not asking about the problem I filled as #3228 (not specific to the bootloader actions at startup), but the equivalent ambiguity for the low level combos being discussed here (with BOOT_KEY_COMBO_POSITIONS specific to startup).

@rianadon
Copy link
Copy Markdown
Author

rianadon commented Mar 2, 2026

@rianadon I suspect you're not asking about the problem I filled as #3228 (not specific to the bootloader actions at startup), but the equivalent ambiguity for the low level combos being discussed here (with BOOT_KEY_COMBO_POSITIONS specific to startup).

I was referring to the issues @joelspadin and @caksoylar brought up in their discussion above, but your problem does sound relevant. Even though we are working at different layers, the user-facing configuration for combos and bootmagic should ideally be very similar.

@rianadon
Copy link
Copy Markdown
Author

I've revised the configuration of the boot magic combos to happen at the physical layout layer instead of the global layer as @caksoylar has suggested. I think overall this is the cleanest approach, and it also makes working with split keyboards nicer.

Here's how the example in the docs for boot magic key configuration on a split keyboard with two physical layouts looks:

shield.dtsi

/ {
    chosen {
        zmk,physical-layout = &default_layout;
    };

    default_layout: default_layout {
        compatible = "zmk,physical-layout";
        display-name = "Default Layout";
        transform = <&default_transform>;
    };

    five_column_layout: five_column_layout {
        compatible = "zmk,physical-layout";
        display-name = "5-Column Layout";
        transform = <&five_column_transform>;
    };

    bootloader_key_left: bootloader_key_left {
        compatible = "zmk,boot-magic-combo";
        combo-positions = <0>;
        jump-to-bootloader;
    };

    bootloader_key_right: bootloader_key_right {
        compatible = "zmk,boot-magic-combo";
        combo-positions = <11>;
        jump-to-bootloader;
    };

    bootloader_key_right_fivecol: bootloader_key_right_fivecol {
        compatible = "zmk,boot-magic-combo";
        combo-positions = <9>;
        jump-to-bootloader;
    };
    ...
};

shield_left.overlay

#include "shield.dtsi"

&default_layout {
    boot-magic-combos = <&bootloader_key_left>;
};
&five_column_layout {
    boot-magic-combos = <&bootloader_key_left>;
}

shield_right.overlay

#include "shield.dtsi"

&default_layout {
    boot-magic-combos = <&bootloader_key_right>;
};
&five_column_layout {
    boot-magic-combos = <&bootloader_key_right_fivecol>;
}

shield.keymap

// Uncomment this block if using the 5-column layout
// / {
//     chosen {
//         zmk,physical-layout = &five_column_layout;
//     };
// };

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants